package org.rcgwt.generators;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.digester.Digester;
import org.apache.commons.digester.Rule;
import org.rcgwt.client.ui.fields.ValidatedTextField;
import org.rcgwt.client.ui.validator.IClientValidator;
import org.rcgwt.client.ui.validator.Parameter;
import org.rcgwt.client.ui.validator.ValidatorRepositoryFactory;
import org.xml.sax.SAXException;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

/**
 * A source code generator which can generate a ClientValidatorRepository (which
 * contains ClientValidator List) and many ClientValidator from many XML
 * configuration files. Configuration files paths have to be placed in the GWT
 * module configuration file.
 * 
 * @author Elian ORIOU
 * @version 1.0
 * 
 */

public class ValidatorRepositoryGenerator extends Generator {

	private static final String REPOSITORY_PACKAGE = "org.rcgwt.client.ui.validator";
	private static final String REPOSITORY_NAME = "ValidatorRepository";
	private static final String VALIDATOR_PACKAGE = "org.rcgwt.client.ui.validator.impl";
	private TypeOracle typeOracle;

	// The default configuration file path
	private static final String DEFAULT_VALIDATOR_SUFFIX = "Validator";
	private static final String DEFAULT_CONFIG_FILE_SUFFIX = "rcgwt-va.xml";

	// Validators List
	private Map<String, ClientValidatorImpl> validators;

	// GWT logger
	private TreeLogger alogger;
	private GeneratorContext acontext;

	@Override
	public String generate(TreeLogger logger, GeneratorContext context,
			String typeName) throws UnableToCompleteException {

		this.alogger = logger;
		this.acontext = context;

		Log.info("Starting ValidatorRepository Generator !");

		// /////////////////////
		// /VALIDATORS CREATION
		// /////////////////////

		try {
			for (String url : getOrderedInputStreams()) {
				parseConfig(url);
			}
		} catch (IOException e) {

		} catch (SAXException e) {

		}

		/* The type determination object */
		typeOracle = acontext.getTypeOracle();
		assert (typeOracle != null);

		/* If no validators has been defined */
		if (validators == null) {
			Log.error("The Validators list is null");
			return null;
		}

		// ///////////////////////////////////
		// /VALIDATOR REPOSITORY GENERATION
		// ///////////////////////////////////

		// Class composer
		ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(
				REPOSITORY_PACKAGE, REPOSITORY_NAME);
		// Factory PrintWriter
		PrintWriter pw = acontext.tryCreate(logger, REPOSITORY_PACKAGE,
				REPOSITORY_NAME);
		composer.addImplementedInterface(ValidatorRepositoryFactory.class
				.getCanonicalName());
		composer.addImport(ValidatedTextField.class.getName());
		composer.addImport(Map.class.getName());
		composer.addImport(HashMap.class.getName());
		composer.addImport(IClientValidator.class.getName());
		composer.addImport(Window.class.getName());
		composer.addImport(Log.class.getName());

		for (ClientValidatorImpl cvi : validators.values()) {
			composer.addImport(VALIDATOR_PACKAGE + "." + cvi.getClassName());
		}

		// Generate the repository
		if (pw != null) {
			// Source writer
			SourceWriter sw = composer.createSourceWriter(acontext, pw);

			// The constructor
			sw.println("public static final " + REPOSITORY_NAME
					+ " SINGLETON=new " + REPOSITORY_NAME + "();");
			sw
					.println("private final Map<String, IClientValidator> validators;");
			sw.println("private " + REPOSITORY_NAME + "() {");
			sw.println("validators=new HashMap<String, IClientValidator>();");
			for (ClientValidatorImpl validatorType : validators.values()) {
				String validatorName = validatorType.getValidatorName();
				String validatorClassName = validatorType.getClassName();
				sw.println("validators.put(\"" + validatorName + "\", new "
						+ validatorClassName + "());");
			}

			// The ApplyValidator method
			sw.println("}");
			sw.println("");
			sw
					.println("public void applyValidator(ValidatedTextField textBox, String validatorName) {");
			sw
					.println("IClientValidator validator = validators.get(validatorName);");
			sw.println("if(validator==null) {");
			sw
					.println("Log.error(\"The Client Validator '\" + validatorName + \"' doesn't exists\");");
			sw.println("return;");
			sw.println("}");
			sw.println("validator.initialize(textBox);");
			sw.println("}");

			sw.commit(alogger);
		}
		/* Finally creates validators */
		createValidators(validators);
		return composer.getCreatedClassName();
	}

	@SuppressWarnings("deprecation")
	private List<String> getOrderedInputStreams() throws IOException,
			SAXException {

		Map<Integer, String> confs = new HashMap<Integer, String>();

		for (String path : acontext.getResourcesOracle().getPathNames()) {
			if (path.endsWith(DEFAULT_CONFIG_FILE_SUFFIX)) {

				String url = acontext.getResourcesOracle().getResourceMap()
						.get(path).getURL().getPath();

				InputStream ins = new FileInputStream(url);

				Digester digester = new Digester();
				digester.addObjectCreate("ClientValidators",
						ClientValidatorConfiguration.class);
				digester.addSetProperties("ClientValidators", "priority",
						"priority");
				ClientValidatorConfiguration cvc = (ClientValidatorConfiguration) digester
						.parse(ins);
				ins.close();

				confs.put(cvc.getPriority(), url);
			}
		}
		List<String> streams = new ArrayList<String>();
		List<Integer> priorities = new ArrayList<Integer>(confs.keySet());

		Collections.sort(priorities);
		for (int p : priorities) {
			streams.add(confs.get(p));
		}
		return streams;
	}

	/**
	 * Parse the XML configuration file to gather validators configuration
	 * 
	 * @param xml
	 *            The XML configuration file content
	 * @throws SAXException
	 * @throws IOException
	 * @throws SAXException
	 */

	private void parseConfig(String url) throws IOException, SAXException {

		InputStream file = new FileInputStream(url);

		if (validators == null) {
			validators = new HashMap<String, ClientValidatorImpl>();
		}

		Digester digester = new Digester();

		/* Storing the ClientValidator element in a ClientValidatorImpl object */
		digester.addObjectCreate("ClientValidators/ClientValidator",
				ClientValidatorImpl.class);

		/* Storing validators into the ClientValidator List */
		digester.addRule("ClientValidators/ClientValidator", new Rule() {
			@Override
			public void end(String namespace, String name) throws Exception {

				ClientValidatorImpl validator = (ClientValidatorImpl) getDigester()
						.peek();
				validators.put(validator.getValidatorName(), validator);
			}
		});

		/* Storing ValidatorsElementsClasses into the ClientValidatorImpl Object */
		digester.addSetProperties("ClientValidators/ClientValidator", "name",
				"validatorName");
		digester.addSetProperties("ClientValidators/ClientValidator",
				"extends", "superValidatorName");

		digester.addSetProperties("ClientValidators/ClientValidator/filter",
				"class", "filterClass");
		digester.addSetProperties(
				"ClientValidators/ClientValidator/translator", "class",
				"translatorClass");
		digester.addSetProperties("ClientValidators/ClientValidator/checker",
				"class", "checkerClass");
		digester.addSetProperties("ClientValidators/ClientValidator/formatter",
				"class", "formatterClass");
		digester.addSetProperties("ClientValidators/ClientValidator/converter",
				"class", "converterClass");

		/* Masker */
		digester.addSetProperties("ClientValidators/ClientValidator/masker",
				"class", "maskerClass");
		digester.addSetProperties("ClientValidators/ClientValidator/masker",
				"pattern", "maskerPattern");
		digester.addSetProperties("ClientValidators/ClientValidator/masker",
				"replaced-by", "maskerReplacedBy");

		/* Storing parameters */
		/* Generic Parameters */
		digester.addObjectCreate("ClientValidators/ClientValidator/parameter",
				Parameter.class);

		/* Calling generic ParameterAdd method */
		digester.addSetNext("ClientValidators/ClientValidator/parameter",
				"addGenericParameter");

		/* Specific Parameters */
		digester.addObjectCreate(
				"ClientValidators/ClientValidator/filter/parameter",
				Parameter.class);
		digester.addObjectCreate(
				"ClientValidators/ClientValidator/translator/parameter",
				Parameter.class);
		digester.addObjectCreate(
				"ClientValidators/ClientValidator/checker/parameter",
				Parameter.class);
		digester.addObjectCreate(
				"ClientValidators/ClientValidator/formatter/parameter",
				Parameter.class);
		digester.addObjectCreate(
				"ClientValidators/ClientValidator/converter/parameter",
				Parameter.class);

		/* Calling Specific ParameterAdd method */

		digester.addSetNext(
				"ClientValidators/ClientValidator/filter/parameter",
				"addFilterParameter");

		digester.addSetNext(
				"ClientValidators/ClientValidator/translator/parameter",
				"addTranslatorParameter");

		digester.addSetNext(
				"ClientValidators/ClientValidator/checker/parameter",
				"addCheckerParameter");

		digester.addSetNext(
				"ClientValidators/ClientValidator/formatter/parameter",
				"addFormatterParameter");

		digester.addSetNext(
				"ClientValidators/ClientValidator/converter/parameter",
				"addConverterParameter");

		/* Storing parameters (name & value) into a Parameter Object */

		digester.addSetProperties("ClientValidators/ClientValidator/parameter",
				"name", "name");
		digester.addSetProperties("ClientValidators/ClientValidator/parameter",
				"value", "value");

		digester.addSetProperties(
				"ClientValidators/ClientValidator/filter/parameter", "name",
				"name");
		digester.addSetProperties(
				"ClientValidators/ClientValidator/filter/parameter", "value",
				"value");

		digester.addSetProperties(
				"ClientValidators/ClientValidator/translator/parameter",
				"name", "name");
		digester.addSetProperties(
				"ClientValidators/ClientValidator/translator/parameter",
				"value", "value");

		digester.addSetProperties(
				"ClientValidators/ClientValidator/checker/parameter", "name",
				"name");
		digester.addSetProperties(
				"ClientValidators/ClientValidator/checker/parameter", "value",
				"value");

		digester.addSetProperties(
				"ClientValidators/ClientValidator/formatter/parameter", "name",
				"name");
		digester.addSetProperties(
				"ClientValidators/ClientValidator/formatter/parameter",
				"value", "value");

		digester.addSetProperties(
				"ClientValidators/ClientValidator/converter/parameter", "name",
				"name");
		digester.addSetProperties(
				"ClientValidators/ClientValidator/converter/parameter",
				"value", "value");

		digester.addSetProperties("ClientValidators/ClientValidator/masker",
				"pattern", "pattern");
		digester.addSetProperties("ClientValidators/ClientValidator/masker",
				"replaced-by", "replacedBy");

		/* Parsing the xml configuration file */

		digester.parse(file);
		setClassNames(DEFAULT_VALIDATOR_SUFFIX);
		file.close();
	}

	/**
	 * Sets the validator class name : The validator name in uppercase (for the
	 * first character) followed by a suffix
	 * 
	 * @param suffix
	 *            The suffix to insert at the queue of the class name
	 */

	public void setClassNames(String suffix) {
		/* Sets class name depending on the validator name */
		for (ClientValidatorImpl v : validators.values()) {
			String firstChar = String.valueOf(v.getValidatorName().charAt(0))
					.toUpperCase();
			String others = v.getValidatorName().substring(1);
			String className = firstChar + others;
			v.setClassName(className + suffix);
		}
		/* Sets super class name depending on the super validator name */

		for (ClientValidatorImpl v : validators.values()) {
			setSuperClassNames(v);
		}
	}

	/**
	 * Sets the super class name (A validator can extends an another)
	 * 
	 * @param v
	 *            The child client validator
	 */

	public void setSuperClassNames(ClientValidatorImpl v) {
		/* If the extends field is null, the operation is stopped */
		if (v.getSuperValidatorName() == null) {
			return;
		}
		boolean isExisting = false;
		String superName = v.getSuperValidatorName();
		/* Search validator thats correspond to the super validator name */
		for (ClientValidatorImpl cvi : validators.values()) {
			if (cvi.getValidatorName().equals(superName)
					&& cvi.getClassName() != null) {
				v.setSuperClassName(VALIDATOR_PACKAGE + "."
						+ cvi.getClassName());
				isExisting = true;
			}
		}
		/* If the super validator name doesn't exists a Log (Warn level) is sent */
		if (isExisting == false) {
			Log.warn("The inherited validator \"" + superName + "\" (for \""
					+ v.getValidatorName() + "\") doesn't exists !");
		}
	}

	/**
	 * Generates Validators source codes
	 * 
	 * @param validators
	 *            The validators list
	 */

	public void createValidators(Map<String, ClientValidatorImpl> validators) {
		int i = 0;
		/* Setting super objects */
		for (ClientValidatorImpl v : validators.values()) {
			v.setParent(validators.get(v.getValidatorName()));
		}
		/* Creating a validator */
		for (ClientValidatorImpl v : validators.values()) {
			v.createValidator(acontext, alogger, VALIDATOR_PACKAGE);
			i++;
		}
		Log.info(i + " Validator(s) Generated");
	}
}
